#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <memory>
#include <string>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
#include <unordered_set>
#include "public.h"
#include <strings.h>
#include <dirent.h>
#include <unordered_map>
#include "common.h"
#include "mysql.h"
#include<sys/stat.h>
#include<fcntl.h>
#include <sys/types.h>		//定义了一些常用数据类型，比如size_t
#include <fcntl.h>			//定义了open、creat等函数，以及表示文件权限的宏定义
#include <unistd.h>			//定义了read、write、close、lseek等函数
#include <sys/ioctl.h>		//定义了ioctl函数 
#include "md5.h"

using namespace std;

// 管理员默认密码
string admin_pwd = "123";

void cmd_ls_process(int clientfd, CommandData &data)
{
    // 读出参数
    char dir[128] = {0};
    strcpy(dir, data.args);

    DIR *pdir = opendir(dir);
    if (pdir == NULL)
    {
        perror("opendir err");
        return;
    }

    string response;
    dirent *pdirent = NULL;
    while ((pdirent = readdir(pdir)) != NULL)
    {
        if (strncmp(pdirent->d_name, ".", 1) == 0 
            || strncmp(pdirent->d_name, "..", 2) == 0)
        {
            continue;
        }
        
        if (pdirent->d_type == DT_DIR)
        {
            response.append("d:");
            response.append(pdirent->d_name);
            response.append("\n");
        }
        else if (pdirent->d_type == DT_REG)
        {
            response.append("f:");
            response.append(pdirent->d_name);
            response.append("\n");
        }
    }
    closedir(pdir);

    int ret = SendPacket(clientfd, TYPE_ACK, (void*)response.c_str(), response.size());
    CheckErr(ret, "SendPacket err");
}

void cmd_rm_process(int clientfd, CommandData &data)
{
    string filepath(data.args);
    filepath = Trim(filepath);

    // 检测filename的合法性
    if (access(filepath.c_str(), F_OK) != 0)
    {
        // 响应文件不合法
        int ret = SendPacket(clientfd, TYPE_ACK);
        CheckErr(ret, "SendPacket err");
        return;
    }

    // 文件合法，让客户端鉴权
    int ret = SendPacket(clientfd, TYPE_AUTH);
    CheckErr(ret, "SendPacket err");

    char buf[1024] = {0};
    DataPacket *dp;
    ret = RecvPacket(clientfd, buf, 1024);
    CheckErr(ret, "RecvPacket err");
    dp = (DataPacket*)buf;

    // begin, end
    string pwd(dp->data, dp->data+dp->datalen);
    if (pwd == admin_pwd)
    {
        // 密码ok，删除文件返回
        if (unlink(filepath.c_str()) < 0)
        {
            ret = SendPacket(clientfd, TYPE_ACK, (void*)"fail", strlen("fail"));
            CheckErr(ret, "SendPacket err");
        }
        else
        {
            ret = SendPacket(clientfd, TYPE_ACK, (void*)"ok", strlen("ok"));
            CheckErr(ret, "SendPacket err");
        }
    }
    else
    {
        // 密码匹配失败
        ret = SendPacket(clientfd, TYPE_ACK, (void*)"fail", strlen("fail"));
        CheckErr(ret, "SendPacket err");
    }
}

void cmd_cd_process(int clientfd, CommandData &data)
{
    //获取当前路径，转为string对象
    string filepath(data.args);
    //去掉多余空格
    filepath = Trim(filepath);
    //如果文件合法
    if (access(filepath.c_str(), F_OK) == 0)
    {
        // 回复ok
        string re = "ok";
        int ret = SendPacket(clientfd, TYPE_ACK, (void*)re.c_str(), re.size());
        CheckErr(ret, "SendPacket err");
        return;
    }
    //文件不存在也需要给回复
    else
    {
        string re = "false";
        int ret = SendPacket(clientfd, TYPE_ACK, (void*)re.c_str(), re.size());
        CheckErr(ret, "SendPacket err");
        return;
    }
    
}

// 类似表驱动的功能
unordered_map<string, void(*)(int, CommandData&)> cmdMap =
{
    {"ls", cmd_ls_process},
    {"rm", cmd_rm_process},
    {"cd", cmd_cd_process},
};

// 增加三个函数，分别处理不同的业务
void ProcessDir(int clientfd, DataPacket *dp)
{
    CommandData data;
    memcpy(&data, dp->data, dp->datalen);

    // 把data.cmd命令前后多余的空格过滤一下
    string cmd = data.cmd;
    cmd = Trim(cmd);
    if (cmd.size() == 0)
    {
        return;
    }
    auto it = cmdMap.find(cmd);
    if (it == cmdMap.end())
    {
        cout << "不合法的命令:" << it->first << endl;
        return;
    }
    // 执行命令对应的函数
    it->second(clientfd, data);
}

// 处理文件上传的命令
void ProcessUpload(int clientfd, DataPacket *dp)
{
    // 取出网络包里面的data数据
    UploadFile uploadFile;
    memcpy(&uploadFile, dp->data, dp->datalen);
    int ret = 0;

    // name   path   md5   ./dir/c++primer
    string filePath = uploadFile.filepath;
    int idx = filePath.find_last_of("/");
    if (idx == string::npos)
    {
        string response = "不合法的文件路径:" + filePath;
        ret = SendPacket(clientfd, TYPE_ACK, (void*)response.c_str(), response.size());
        CheckErr(ret, "SendPacket err");
        return;
    }

    string path = filePath.substr(0, idx+1);
    string name = filePath.substr(idx+1, filePath.size()-idx-1);
    string md5 = uploadFile.md5;
    uint64 filesize = uploadFile.filesize;

    // 在DB里面查询文件信息
    /*
    1. path + name 不存在， 普传
    2. path + name 存在
                state == '1' and md5 == md5 秒传
                state == '0' 断点续传
    */
    DB db;
    char sql[1024] = {0};
    snprintf(sql, 1024, "select state,md5,blockno from file where name='%s' and path='%s'"
        ,name.c_str(), path.c_str());
    vector<vector<string>> vecs = db.query(sql);
    cout << "vecs size:" << vecs.size() << endl;
    if (vecs.empty())
    {
        // 普通传输  blockno：0
        UploadFileAck ack;
        ack.type = TYPE_NORMAL;
        ack.blockno = 0;
        ret = SendPacket(clientfd, TYPE_ACK, (void*)&ack, sizeof(ack));
        CheckErr(ret, "SendPacket err");
    }
    else
    {
        cout << "state:" << vecs[0][0] << endl;
        cout << "md5:" << vecs[0][1] << endl;
        // name+path已经存在，遍历vecs，找state == '1' and md5相等，秒传
        if (vecs[0][0] == "1" && vecs[0][1] == md5)
        {
            // 秒传
            UploadFileAck fileack;
            fileack.type = TYPE_QUICK;
            ret = SendPacket(clientfd, TYPE_ACK, (void*)&fileack, sizeof(fileack));
            CheckErr(ret, "SendPacket err");
            return;
        }
        else if (vecs[0][0] == "1")
        {
            // 文件名一样, md5不一样，改名字的普传
            UploadFileAck fileack;
            fileack.type = TYPE_NORMAL;
            fileack.blockno = 1;
            ret = SendPacket(clientfd, TYPE_ACK, (void*)&fileack, sizeof(fileack));
            CheckErr(ret, "SendPacket err");
			name = name + "-" + md5;
        }
        else
        {
            // state = '0' 断点续传，返回正确的blockno, 普传的流程相似
            UploadFileAck fileack;
            fileack.type = TYPE_NORMAL;
            fileack.blockno = stoi(vecs[0][2])+1;
            ret = SendPacket(clientfd, TYPE_ACK, (void*)&fileack, sizeof(fileack));
            CheckErr(ret, "SendPacket err");
        }
    }

    // 开始循环接收client发过来的文件数据   记录已经传输成功的block块数量
    // 每次接收一个块，写入到文件当中，然后给block++
    const int size = sizeof(DataPacket) + sizeof(TransferFile);
    char buf[size];
    int blocknum = 0;
    uint64 recvsize = 0;
    double progress = 0.0;

    // 打开文件   
    // O_APPEND：以追加写入方式打开文件，打开之后文件指针指向文件末尾，文件不存在就创建新文件
    int fd = 0;
    if (access((path+name).c_str(), F_OK) == 0)
    {
        fd = open((path+name).c_str(), O_APPEND | O_RDWR);
    }
    else
    {
        fd = open((path+name).c_str(), O_CREAT | O_RDWR, 0755);
    }
	CheckErr(fd, "open err");
    for (;;)
    {
        //清空buf
        bzero(buf, size);
        // 接收一下  ret的值  >0   0  -1
        ret = RecvPacket(clientfd, buf, size);
        CheckErr(ret, "RecvPacket err");
        if (ret == 0)
        {
            // 客户端断开
            break;
        }

        DataPacket *dp = (DataPacket*)buf;
        // 写文件  write
        TransferFile *tfile = (TransferFile*)(dp->data);
		int writeSize = dp->datalen-sizeof(int);
        ret = write(fd, tfile->content, writeSize);
        CheckErr(ret, "write err.");
        // 记录blocknum块 +1
        blocknum++;
        recvsize += writeSize;
        
        char buf[128] = {0};
        while ((recvsize*1.0 / filesize)*100 > progress)
        {
            progress += 10.0;
            snprintf(buf, 128, "%%%d", progress);// 输出%d前面要有两个%%
        }

        // 返回ack带进度
        ret = SendPacket(clientfd, TYPE_ACK, (void*)buf, strlen(buf));
        CheckErr(ret, "SendPacket err");

        // 判断收结束，退出循环
        cout << "recvsize:" << recvsize << " filesize:" << filesize << endl;
        if (recvsize == filesize)
        {
            break;
        }
    }
    // 关闭文件
    close(fd);

    // 在数据库中记录 blocknum
    //snprintf(sql, 1024, "update user set blockno=blocknum  where name='%s' and path='%s'",name.c_str(), path.c_str());
    //db.update(sql);
    if (recvsize == filesize)
    {
        // 在server端重新生成该文件的md5值，和上面的md5对比
        cout << "file:" << (path+name).c_str() << endl;
        char* smd5 = MD5_file((path+name).c_str());
        auto_ptr<char> ptr(smd5);

        // md5相同，响应ack文件传输成功，写一次db state='1' blockno=block
        cout << "smd5:" << smd5 << endl;
        cout << "md5:" << md5.c_str() << endl;
        if (strcmp(smd5, md5.c_str()) == 0)
        {
            // 判断  if (vecs.empty())  文件是否存在，
            if (vecs.empty())
            {
                // 写入需要插入的内容
				snprintf(sql, 1024, "insert into file(name, path, md5, state, blockno) values('%s', '%s', '%s', '%s', %d)",
					name.c_str(), path.c_str(), md5.c_str(), "1", blocknum);
                db.update(sql);
            }
            else
            {
                // 写数据库
                snprintf(sql, 1024, "update file set state='1',blockno=%d where name='%s' and path='%s'",
					blocknum, name.c_str(), path.c_str());
                db.update(sql);
            }
            // 响应客户端
            ret = SendPacket(clientfd, TYPE_ACK, (void*)"ok", strlen("ok"));
            CheckErr(ret, "SendPacket err");

        }
        else  // md5不相同，响应ack文件传输失败,删除文件
        {
            // 响应客户端
            ret = SendPacket(clientfd, TYPE_UPLOAD, (void*)"fail", strlen("fail"));
            CheckErr(ret, "SendPacket err");
			
			// 删除本地文件
            // remove((path+name).c_str());
        }
    }
    else
    {
         // 上面循环接收文件数据因为client中断，提前跳出，写一次db state='0' blockno=block
         // 写数据库
		if (vecs.empty())
		{
			snprintf(sql, 1024, "insert into file values(name, path, md5, state, blockno) values('%s', '%s', '%s', '%s', %d)",
					name.c_str(), path.c_str(), md5.c_str(), "0", blocknum);
            db.update(sql);
		}
        else
		{
			snprintf(sql, 1024, "update file set blockno=%d where name='%s' and path='%s'"
				,blocknum, name.c_str(), path.c_str());
			db.update(sql);
		}
    }
}